package cn.isjinhao.crws.sql.grammar;

import cn.isjinhao.crws.sql.grammar.create.represention.Insert;
import cn.isjinhao.crws.sql.grammar.create.represention.InsertSetRep;
import cn.isjinhao.crws.sql.grammar.create.represention.OnDuplicateKey;
import cn.isjinhao.crws.sql.grammar.create.represention.ValuesRep;
import cn.isjinhao.crws.sql.grammar.retrieve.SingleSelectType;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.Select;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.SingleSelect;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.column.ColumnRep;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.column.ColumnSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.group.GroupBySeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.limit.LimitSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.order.OrderBySeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.table.TableRep;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.table.TableSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.ExistsCondition;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.InCondition;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.RetrieveWhereSeg;
import cn.isjinhao.crws.sql.lexical.token.Token;
import cn.isjinhao.crws.sql.lexical.token.TokenType;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author ISJINHAO
 * @Date 2021/3/6 15:31
 */
public class CurdStatementPrinter implements CurdStatementVisitor<String> {

    private int deepth = -1;

    @Override
    public String visitSelect(Select select) {
        return visitBinary(select, "\n").toString();
    }

    @Override
    public String visitSingleSelect(SingleSelect singleSelect) {
        StringBuilder sb = new StringBuilder();
        SingleSelectType singleSelectType = singleSelect.getSingleSelectType();
        boolean needParen = !singleSelectType.equals(SingleSelectType.TOP);
        if(needParen) {
            sb.append(" ( ");
        }

        deepth++;

        sb.append(blankStr()).append("select\t").append(singleSelect.getColumnSeg().accept(this)).append("\n");
        sb.append(blankStr()).append("from\t").append(singleSelect.getTableSeg().accept(this)).append("\n");

        CurdStatement whereSeg = singleSelect.getWhereSeg();
        if (whereSeg != null) {
            sb.append(blankStr()).append("where\t").append(whereSeg.accept(this)).append("\n");
        }

        CurdStatement groupBySeg = singleSelect.getGroupBySeg();
        if (groupBySeg != null) {
            sb.append(blankStr()).append("group by\t").append(groupBySeg.accept(this)).append("\n");
        }

        CurdStatement orderBySeg = singleSelect.getOrderBySeg();
        if (orderBySeg != null) {
            sb.append(blankStr()).append("order by\t").append(orderBySeg.accept(this)).append("\n");
        }

        CurdStatement limitSeg = singleSelect.getLimitSeg();
        if (limitSeg != null) {
            sb.append(blankStr()).append("limit\t").append(limitSeg.accept(this)).append("\n");
        }
        deepth--;
        if(needParen) {
            sb.append(" ) ");
        }
        return sb.toString();
    }

    @Override
    public String visitColumnSeg(ColumnSeg columnSeg) {

        StringBuilder sb = new StringBuilder();
        Token restriction = columnSeg.getRestriction();
        if (restriction != null) {
            sb.append(restriction.getLiteral()).append(" ");
        }

        List<CurdStatement> columnRepList = columnSeg.getColumnRepList();
        sb.append(columnRepList.stream().map(item -> item.accept(this))
                            .collect(Collectors.joining(", ")));
        return sb.toString();
    }

    @Override
    public String visitColumnRep(ColumnRep columnRep) {
        StringBuilder sb = new StringBuilder();
        CurdStatement curdStatement = columnRep.getSelectStatement();
        sb.append(curdStatement.accept(this));

        Token operator = columnRep.getOperator();
        if (operator != null) {
            sb.append(" as ").append(operator.getLiteral());
        }
        return sb.toString();
    }

    @Override
    public String visitTableSeg(TableSeg tableSeg) {
        StringBuilder sb = visitBinary(tableSeg, " ");
        CurdStatement condition = tableSeg.getCondition();
        if (condition != null) {
            sb.append(" on ").append(condition.accept(this));
        }
        return sb.toString();
    }

    @Override
    public String visitTableRep(TableRep tableRep) {
        StringBuilder sb = new StringBuilder();
        CurdStatement curdStatement = tableRep.getSelectStatement();
        Token operator = tableRep.getOperator();
        String accept = curdStatement.accept(this);
        if (curdStatement instanceof SingleSelect) {
            sb.append(accept).append(" ");
            sb.append(operator.getLiteral());
        } else if(operator == null && curdStatement instanceof Identifier) {
            sb.append(accept);
        } else if(operator != null && curdStatement instanceof Identifier) {
            sb.append(accept).append(" ").append(operator.getLiteral());
        } else {
            sb.setLength(0);
        }

        return sb.toString();
    }

    @Override
    public String visitRetrieveWhereSeg(RetrieveWhereSeg retrieveWhereSeg) {
        return retrieveWhereSeg.getSelectStatement().accept(this);
    }

    @Override
    public String visitInCondition(InCondition inCondition) {
        StringBuilder sb = new StringBuilder();
        Token identifier = inCondition.getIdentifier();
        sb.append(identifier.getLiteral());

        Token in = inCondition.getIn();
        if (in.getType().equals(TokenType.IN)) {
            sb.append(" in ");
        } else {
            sb.append(" not in ");
        }
        CurdStatement curdStatement = inCondition.getSelectStatement();
        if (curdStatement != null) {
            sb.append(curdStatement.accept(this));
        } else {
            sb.append("( ");
            List<CurdStatement> range = inCondition.getRange();
            if (range != null && !range.isEmpty()) {
                sb.append(range.stream()
                        .map(item -> item.accept(this))
                        .collect(Collectors.joining(", ")));
            }
            sb.append(")");
        }
        return sb.toString();
    }

    @Override
    public String visitExistsCondition(ExistsCondition existsCondition) {
        StringBuilder sb = new StringBuilder();
        Token operator = existsCondition.getOperator();
        if (operator.getType().equals(TokenType.EXISTS)) {
            sb.append(" exists ");
        } else {
            sb.append(" not exists ");
        }
        CurdStatement curdStatement = existsCondition.getSelectStatement();
        sb.append(curdStatement.accept(this));
        return sb.toString();
    }

    @Override
    public String visitGroupBySeg(GroupBySeg groupBySeg) {
        StringBuilder sb = new StringBuilder();
        List<Token> columnList = groupBySeg.getColumnList();
        if (columnList != null && !columnList.isEmpty()) {
            sb.append(columnList.stream()
                    .map(item -> item.getLiteral().toString())
                    .collect(Collectors.joining(", ")));
        }
        CurdStatement having = groupBySeg.getHaving();
        if (having != null) {
            sb.append(" having ").append(having.accept(this));
        }
        return sb.toString();
    }

    @Override
    public String visitOrderBySeg(OrderBySeg orderBySeg) {
        StringBuilder sb = new StringBuilder();
        List<OrderBySeg.OrderItem> columnList = orderBySeg.getColumnList();
        if (columnList != null && !columnList.isEmpty()) {
            OrderBySeg.OrderItem orderItem = columnList.get(0);
            Token column = orderItem.getColumn();
            Token orderType = orderItem.getOrderType();
            sb.append(column.getLiteral() + " " + (orderType == null ? "" : orderType.getLiteral()));
            for (int i = 1; i < columnList.size(); i++) {
                orderItem = columnList.get(i);
                column = orderItem.getColumn();
                orderType = orderItem.getOrderType();
                sb.append(", " + column.getLiteral() + " " + (orderType == null ? "" : orderType.getLiteral()));
            }
        }
        return sb.toString();
    }

    @Override
    public String visitLimitSeg(LimitSeg limitSeg) {
        StringBuilder sb = new StringBuilder();
        sb.append(limitSeg.getNum().getLiteral() + ", " + limitSeg.getOffset().getLiteral());
        return sb.toString();
    }

    @Override
    public String visitLogic(Logic logic) {
        return visitBinary(logic, " ").toString();
    }

    @Override
    public String visitComparison(Comparison comparison) {
        return visitBinary(comparison, " ").toString();
    }

    @Override
    public String visitBinaryArithmetic(BinaryArithmetic binaryArithmetic) {
        return visitBinary(binaryArithmetic, " ").toString();
    }

    @Override
    public String visitUnaryArithmetic(UnaryArithmetic unaryArithmetic) {
        StringBuilder sb = new StringBuilder();

        sb.append(unaryArithmetic.getOperator().getLiteral())
                .append(unaryArithmetic.getSelectStatement().accept(this));

        return sb.toString();
    }

    @Override
    public String visitLiteral(Literal literal) {
        if (literal.getValue() == null) {
            return null;
        }
        return literal.getValue().toString();
    }

    @Override
    public String visitGroupFunction(GroupFunction groupFunction) {
        StringBuilder sb = new StringBuilder();
        sb.append(groupFunction.getMethod().getLiteral())
                .append("( ")
                .append(groupFunction.getToken().getLiteral())
                .append(" )");
        return sb.toString();
    }

    @Override
    public String visitGrouping(Grouping grouping) {
        return "( " + grouping.getSelectStatement().accept(this) + " )";
    }

    @Override
    public String visitIdentifier(Identifier identifier) {
        return identifier.getName().getLiteral().toString();
    }

    @Override
    public String visitFunction(Function function) {
        StringBuilder sb = new StringBuilder();
        Token method = function.getMethod();
        List<CurdStatement> parameterList = function.getParameterList();
        sb.append(method.getLiteral());
        if(parameterList == null || parameterList.isEmpty()) {
            sb.append("()");
            return sb.toString();
        }
        sb.append(parameterList.stream().map(item -> item.accept(this)).collect(Collectors.joining(", ", "( ", " )")));
        return sb.toString();
    }

    @Override
    public String visitValuesRep(ValuesRep valuesRep) {
        StringBuilder sb = new StringBuilder();
        List<Token> columnList = valuesRep.getColumnList();
        if(columnList != null && !columnList.isEmpty()) {
            String collect = columnList.stream().map(item -> (String) item.getLiteral()).collect(Collectors.joining(", ", " ( ", " ) "));
            sb.append(collect);
        }

        sb.append("values");

        List<List<CurdStatement>> curdStatementsList = valuesRep.getCurdStatementsList();
        String collect = curdStatementsList.stream()
            .map(i -> i.stream().map(j -> j.accept(this)).collect(Collectors.joining(", ", " ( ", " ) ")))
            .collect(Collectors.joining(", "));

        sb.append(collect);

        return sb.toString();
    }

    @Override
    public String visitSetRep(InsertSetRep insertSetRep) {
        StringBuilder sb = new StringBuilder();

        sb.append("set ");

        CurdStatement entryList = insertSetRep.getEntryList();
        String accept = entryList.accept(this);
        sb.append(accept);

        return sb.toString();
    }

    @Override
    public String visitInsert(Insert insert) {
        StringBuilder sb = new StringBuilder();

        sb.append("insert ");
        Token constrict = insert.getConstrict();
        if(constrict != null) {
            sb.append(constrict.getLiteral());
        }
        sb.append(" into ");

        Token tableName = insert.getOperator();
        sb.append(tableName.getLiteral());

        sb.append(insert.getSelectStatement().accept(this));

        CurdStatement onDuplicateUpdate = insert.getOnDuplicateUpdate();
        if(onDuplicateUpdate != null) {
            sb.append(onDuplicateUpdate.accept(this));
        }

        return sb.toString();
    }

    @Override
    public String visitEntryList(AssignmentList assignmentList) {
        return assignmentList.getEntryList().stream()
            .map(item -> item.getColumnName().getLiteral() + "=" + item.getValue().accept(this))
            .collect(Collectors.joining(", "));
    }

    @Override
    public String visitOnDuplicateKey(OnDuplicateKey onDuplicateKey) {
        StringBuilder sb = new StringBuilder();
        sb.append(" on ").append("duplicate ").append("key ").append("update ").append(onDuplicateKey.getCurdStatement().accept(this));
        return sb.toString();
    }

    private String blankStr() {
        StringBuilder blackStr = new StringBuilder();
        for (int i = 0; i < deepth; i++) {
            blackStr.append("\t");
        }
        return blackStr.toString();
    }

    private StringBuilder visitBinary(Binary binary, String separator) {
        StringBuilder sb = new StringBuilder();

        CurdStatement left = binary.getLeftSelectStatement();
        sb.append(left.accept(this)).append(separator);

        Token token = binary.getToken();
        if(token != null) {
            sb.append(token.getLiteral()).append(separator);
        }

        CurdStatement right = binary.getRightSelectStatement();
        if(right != null) {
            sb.append(right.accept(this)).append(separator);
        }
        return sb;
    }

}
